Existen dos grandes formas de entender el proceso de inversión de un capital, una inversión pasiva, como los portafolios de inversión, y una inversión activa, como estrategias de trading. Al acto de comprar y/o vender un activo financiero se le conoce como “Comerciar” o hacer “trading” y en la actualidad, la gran mayoría de esta actividad se lleva a cabo electrónicamente. Cuando se lleva a cabo un proceso de inversión activa con el objetivo de incrementar el capital, generalmente se recurre a llevar a cabo la actividad de trading con fines especulativos y en una frecuencia diaria, o incluso, cuando es realizada mediante sistemas informáticos, a una frecuencia de milisegundos. Independientemente de la frecuencia, se puede optar por 2 naturaleza de estrategias de trading, una discrecional (generalmente efectuada por profesionales) y una sistemática (generalmente efectuada por sistemas informáticos).
Cuando se plantea el uso de Machine Learning para formular una estrategia de trading de naturaleza sistemática, o también conocida como un sistema de trading algorítmico, en la generalidad de los casos se piensa en modelado predictivo del precio de un activo financiero y se plantea la predicción, bajo la perspectiva de pronósticos de precios futuros, como un problema de regresión (pronóstico del siguiente precio) o de clasificación (pronóstico de un estado alcista o bajista del precio).
Para construir modelos predictivos se pueden utilizar variables de entrada exógenas (fenómenos/procesos externos al activo financiero) y variables de entrada endógenas (transformaciones matemáticas a la misma serie de tiempo del precio). Una vez diseñadas estas variables de entrada a través de un proceso conocido como Ingeniería de Variables, se conducen actividades como Importancia y Selección de variables, al final de los cuales se logra tener variables explicativas candidatas a ser utilizadas como entrada en los modelos propuestos. Es sabido que para modelar procesos financieros como el precio de activos bursátiles se necesita contar tanto con variables lineales como no lineales, así como con modelos de naturaleza lineal y no lineal. En el proceso de uso de variables, ajuste de modelos, optimización y backtest de los hiperparámetros es
Finalmente, las técnicas de validación cruzada son frecuentemente utilizadas en problemas de optimización de modelos, Sin embargo se debe de tener una consideración especial cuando se trata de datos del tipo series de tiempo, esto es debido a que el orden de los datos debe de ser preservado durante el proceso de generación de subconjuntos de entrenamiento y prueba. Para abordar esta situación, se propone utilizar un método conocido como “Blocking Time Series Split” o División por Bloques de Series de Tiempo.
Precios de futuros continuos, variables endógenas de 3 tipos, divisiónm de t-folds, optimización con algoritmos genéticos, estabilidad paramétrica, desempeño final.
Para la elaboración de este proyecto se utilizaron las siguientes liberías, las cuales, es necesario tenerlas instaladas y/o en el archivo requirements.txt con el siguiente contenido :
La distribución de la funcionalidad de todo el proyecto fue un aspecto importante a considerar para mantener modularidad, eficiencia e interpretabilidad en este notebook. La organización del proyecto y las dependencias es la siguiente:
%%capture
"""
- files
- - daily_prices : Precios historicos por día
- - minute_prices : Precios historicos por minuto
- - images : Imagenes Utilizadas en notebook
- - pickle_rick : Resultados para analisis de complejidad
- main.py : Secuencia principal del proyecto
- data.py : Entrada y salida de datos
- functions.py : operaciones, modelos, preprocesamiento de datos
- visualizations.py : graficas
- Genetic_Net_Report.ipynb : Este notebook
- Genetic_Net_Report.html : Version "Stand Alone" del notebook
- venv : Ambiente Virtual del Proyecto
- requirements.txt : Requerimientos de librerias
- README.md : Para repositorio
- .gitignore : Para repositorio
"""
%%capture
pip install -r requirements.txt
Cargar librerias y dependencias a utilizar en este notebook
# -- Librerias necesarias para este notebook
import warnings
from datetime import datetime
import pandas as pd
warnings.filterwarnings("ignore")
# -- Dependencias (scripts) necesarias para este notebook
import visualizations as vs
import data as dt
import functions as fn
Debido a que la naturaleza de este proyecto implicó hacer una búsqueda quasi-exhaustiva de condiciones y utilizando una gran cantidad de datos, el tiempo que demora el proyecto completo en generar los resultados fue de mas de 6 horas por cada variación de condiciones (se hicieron 3 variaciones que mas adelante se explican), por lo tanto, se agregan las siguientes notas:
Lo que se muestra en este notebook, tanto de contenido, como de códigos, es sólo una parte de todo el proyecto. Debido a la naturaleza compleja del planteamiento de la solución y la interconectividad entre funcionalidades construidas, se generaron +2,000 lineas de código. También, las gráficas y tablas que se muestran, en su mayoría, son sólo la visualización de un caso particular de variación. En este trabajo se hicieron 3 variaciones del algoritmo/proceso de modelación general: Periodos Trimestrales, Semestrales y Anuales.
Todos los datos generados en el proceso completo para cada variación se guardaron en archivos tipo "pickle", un formato para almacenamiento de ambientes para python, estos se encuentran en la carpeta pickle_rick.
# All the saved data
memory_palace = dt.data_save_load(p_data_objects=None, p_data_action='load',
p_data_file='files/pickle_rick/Genetic_Net_Quarter.dat')
El dataset principal del proyecto fueron los precios diarios de los contratos futuros M6XXX del tipo de cambio UsdMxn, se hizo una unión calendario de varios contratos para obtener una serie de tiempo de contratos futuros continuos. La representacion de estos precios es la conocida como de velas japonesas o Candlesticks con los precios de OHLC (Open, High, Low, Close). Se tiene precios que abarcan desde 2010-01-03 hasta 2020-10-30
# General dataframe with all the data for the project
general_data = dt.ohlc_data.copy()
# visualizar datos
general_data
# ------------------------------------------------------------- PLOT 1: MXN/USD Historical Future Prices -- #
# --------------------------------------------------------------- -------------------------------------- -- #
# Override plot main title
dt.theme_plot_1['p_labels']['title'] = 'Gráfica 1: <b> Precios Históricos OHLC </b>'
# Plot 1 : time series candlesticks OHLC historical prices
plot_1 = vs.g_ohlc(p_ohlc=general_data, p_theme=dt.theme_plot_1, p_vlines=None)
# Render plot
plot_1
# data description
table_1 = general_data.describe()
# visualize response in console
table_1
Para poder conducir el proceso de optimización y reducir el error de generalización de los modelos predictivos, se utilizó una técnica conocida como validación cruzada, con la consideración especial que los datos son series de tiempo, y que por lo tanto, debido a que el orden si importa, la división de los datos para la validación cruzada es secuencial y sin filtraciones. Esto último significa que, en cada periodo, se conduce el proceso de separación de datos de entrenamiento y prueba, se hace la ingeniería de variables, se hace la optimización con datos de entrenamiento se obtienen métricas tanto para los datos de entrenamiento como los de prueba y se grafican, todo esto sin que los datos de un periodo se "filtren" en los de otro periodo.
Los 3 tamaños de bloques utilizados para la validación cruzada fueron:
%%capture
# in quarters obtain 4 folds for each year
t_folds = fn.t_folds(p_data=general_data.copy(), p_period='quarter')
# drop the last quarter because it is incomplete until december 31
t_folds.pop('q_04_2020', None)
print('Un ejemplo de las primeras 5 llaves del diccionario con todos los T-Folds:', list(t_folds.keys())[0:4])
# construccion de fechas para lineas verticales de division de cada fold
dates_folds = []
for fold in list(t_folds.keys()):
dates_folds.append(t_folds[fold]['timestamp'].iloc[0])
dates_folds.append(t_folds[fold]['timestamp'].iloc[-1])
# Override plot main title
dt.theme_plot_1['p_labels']['title'] = 'Gráfica 2: <b> ' + \
'T-Folds por Bloques Sin Filtraciones para Series de Tiempo </b>'
# grafica OHLC
plot_2 = vs.g_ohlc(p_ohlc=general_data, p_theme=dt.theme_plot_1, p_vlines=dates_folds)
# visualize
plot_2
Se generaron tres tipos de variables explicativas:
Con la función autoregressive_features, son promedios móviles y operadores resago de los precios Open, High, Low, Close y del Volumen
El código de esta operación está en la función hadamard_features y consiste en la multiplicación elemento por elemento (producto Hadamard de dos matrices) de las columnas previamente generadas.
En la función symbolic_features se puede observar el uso de una función especial, SymbolicTransformer, de la librería "GPLearn". Esta función especial es el encapsulamiento de un proceso de programación genética para la generación de "programas" o "ecuaciones simbólicas" que generan nuevos features.
Las operaciones simbólicas utilizadas para la generación de estos features fueron:
memory_palace['ls-svm']['q_01_2010']['features']['train_x'].head(5)
Se utilizaron 3 tipos de modelos predictivos:
Se utilizaron las funcionalidades de la librería scikitlearn : LogisticRegression, SVC, MLPClassifier
Los hiperparametros que se optimizaron para cada modelo, y los valores que se iteraron para cada uno de ellos, fueron los siguientes:
models = {
'ann-mlp': {
'label': 'ann-mlp',
'params': {'hidden_layers': [(5, ), (10, ), (5, 5), (10, 5), (10, 10),
(5, ), (10, ), (5, 5), (10, 5), (10, 10)],
'activation': ['relu', 'relu', 'relu', 'relu', 'relu',
'logistic', 'logistic', 'logistic', 'logistic', 'logistic'],
'alpha': [0.005, 0.1, 0.05, 0.02, 0.01, 0.005, 0.1, 0.05, 0.02, 0.01],
'learning_r': ['constant', 'constant', 'constant', 'constant', 'constant',
'adaptive', 'adaptive', 'adaptive', 'adaptive', 'adaptive'],
'learning_r_init': [0.2, 0.1, 0.02, 0.01, 0.001, 0.2, 0.1, 0.02, 0.01, 0.001]}},
'logistic-elasticnet': {
'label': 'logistic-elasticnet',
'params': {'ratio': [0.05, 0.10, 0.20, 0.30, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00],
'c': [1.5, 1.1, 1, 0.8, 0.5, 1.5, 1.1, 1, 0.8, 0.5]}},
'ls-svm': {
'label': 'ls-svm',
'params': {'c': [1.5, 1.1, 1, 0.8, 0.5, 1.5, 1.1, 1, 0.8, 0.5],
'kernel': ['linear', 'linear', 'linear', 'linear', 'linear',
'rbf', 'rbf', 'rbf', 'rbf', 'rbf'],
'gamma': ['scale', 'scale', 'scale', 'scale', 'scale',
'auto', 'auto', 'auto', 'auto', 'auto']}}}
El proceso de optimización se realizó con algorítmos genéticos, utilizando los constructores de la librería DEAP, la cual, permite especificar funciones hechas a la medida así como utilizar algunas predefinidas que se ofrecen. A continuación se incluye una parte de la función genetic_algo_optimization
# if p_model['label'] == 'logistic-elasticnet':
#
# # borrar clases previas si existen
# try:
# del creator.FitnessMax_en
# del creator.Individual_en
# except AttributeError:
# pass
#
# # inicializar ga
# creator.create("FitnessMax_en", base.Fitness, weights=(1.0,))
# creator.create("Individual_en", list, fitness=creator.FitnessMax_en)
# toolbox_en = base.Toolbox()
#
# # define how each gene will be generated (e.g. criterion is a random choice from the criterion list).
# toolbox_en.register("attr_ratio", random.choice, p_model['params']['ratio'])
# toolbox_en.register("attr_c", random.choice, p_model['params']['c'])
#
# # This is the order in which genes will be combined to create a chromosome
# toolbox_en.register("Individual_en", tools.initCycle, creator.Individual_en,
# (toolbox_en.attr_ratio, toolbox_en.attr_c), n=1)
#
# # population definition
# toolbox_en.register("population", tools.initRepeat, list, toolbox_en.Individual_en)
#
# # -------------------------------------------------------------- funcion de mutacion para LS SVM -- #
# def mutate_en(individual):
#
# # select which parameter to mutate
# gene = random.randint(0, len(p_model['params']) - 1)
#
# if gene == 0:
# individual[0] = random.choice(p_model['params']['ratio'])
# elif gene == 1:
# individual[1] = random.choice(p_model['params']['c'])
#
# return individual,
#
# # --------------------------------------------------- funcion de evaluacion para OLS Elastic Net -- #
# def evaluate_en(eva_individual):
#
# # output of genetic algorithm
# chromosome = {'ratio': eva_individual[0], 'c': eva_individual[1]}
#
# # model results
# model = logistic_net(p_data=p_data, p_params=chromosome)
#
# # True positives in train data
# train_tp = model['results']['matrix']['train'][0, 0]
# # True negatives in train data
# train_tn = model['results']['matrix']['train'][1, 1]
# # Model accuracy
# train_fit = (train_tp + train_tn) / len(model['results']['data']['train'])
#
# # True positives in test data
# test_tp = model['results']['matrix']['test'][0, 0]
# # True negatives in test data
# test_tn = model['results']['matrix']['test'][1, 1]
# # Model accuracy
# test_fit = (test_tp + test_tn) / len(model['results']['data']['test'])
#
# # Fitness measure
# model_fit = np.mean([train_fit, test_fit])
#
# return model_fit,
#
# toolbox_en.register("mate", tools.cxOnePoint)
# toolbox_en.register("mutate", mutate_en)
# toolbox_en.register("select", tools.selTournament, tournsize=10)
# toolbox_en.register("evaluate", evaluate_en)
#
# population_size = 60
# crossover_probability = 0.8
# mutation_probability = 0.1
# number_of_generations = 1
#
# en_pop = toolbox_en.population(n=population_size)
# en_hof = tools.HallOfFame(10)
# stats = tools.Statistics(lambda ind: ind.fitness.values)
# stats.register("avg", np.mean)
# stats.register("std", np.std)
# stats.register("min", np.min)
# stats.register("max", np.max)
#
# # Genetic Algorithm Implementation
# en_pop, en_log = algorithms.eaSimple(population=en_pop, toolbox=toolbox_en, stats=stats,
# cxpb=crossover_probability, mutpb=mutation_probability,
# ngen=number_of_generations,
# halloffame=en_hof, verbose=True)
#
# return {'pop': en_pop, 'logs': en_log, 'hof': en_hof}
# Predictive models
ml_models = list(dt.models.keys())
ml_models
El siguiente es un ejemplo de como se iba imprimiendo en la consola el resultado de las principales operaciones con algoritmos. Podemos observar que para este ejemplo se estaba explorando un tipo de periodicidad semestral, de ahi el inidicativo s_ en el nombre del periodo, El modelo probado estaba siendo la red neuronal tipo perceptrón multicapa.
Sólo se tuvieron que crear 2 generaciones para llegar a encontrar un individuo cuyo cromosoma lograra producir un fitness igual o mayor al especificado, que en este caso era el programa creado generara una variable explicativa que tuviera, por lo menos, un 0.60 de coeficiente de correlación de perason con la variable objetivo (como era binaria, el coeficiente de correlación fue una buena opción de fitness).
En el proceso de optimización, también sólo fueron necesarias 2 generaciones, y de todos los individuos de cada generación, se obtuvieron medidas estadísticas de dispersión y de tendencia central, con la finalidad de observar el mejoramiento "entre generaciones", mas allá de sólo buscar el "mejor" individuo, empíricamente se buscó que se estuvieran teniendo cada ves mejores generaciones y así tener una heurística de estabilidad paraḿetrica.
Finalmente se registró el tiempo que transcurrió este proceso, fue un poco mas de 14 minutos con 42 segundos.
# ----------------------------
# modelo: ann-mlp
# periodo: s_01_2010
# ----------------------------
#
# ----------------------- Ingenieria de Variables por Periodo ------------------------
# ----------------------- ----------------------------------- ------------------------
# | Population Average | Best Individual |
# ---- ------------------------- ------------------------------------------ ----------
# Gen Length Fitness Length Fitness OOB Fitness Time Left
# 0 64.14 0.0784983 3 0.542514 N/A 5.82m
# 1 3.64 0.160435 5 0.605034 N/A 8.77m
#
# --------------------- Optimizacion de hiperparametros por Periodo ------------------
# --------------------- ------------------------------------------- ------------------
# gen nevals avg std min max
# 0 60 0.580082 0.0396069 0.518627 0.755882
# 1 50 0.652168 0.0653966 0.497712 0.766993
#
# Elapsed Time = 0:14:42.882302
Para los 10 años de precios diarios que se exploraron, una vez que estos fueron divididos en T-Folds (trimestral, semestral, anual), para cada periodo se conducian los siguientes subprocesos:
Y se almacenaba el "Hall of Fame" de cada periodo, el cual, consiste en un grupo con los 10 mejores individuos de la última generación obtenida en el periodo de optimización, con estos 10 individuos se evaluaba el modelo con tales parámetros y se almacenaban esos resultados. De tal manera que para encontrar los "mejores de los mejores" se buscaron, en todos los periodos, para los 3 modelos, aquellos individuos o configuraciones de parámetros de los modelos que dieran el mejor AUC y el peor AUC. Mientras mas cercana este la AUC de un modelo al valor de 1, es mejor.
Después de haber conducido el proceso de optimización para todos y cada uno de los periodos, y para todos y cada uno de los modelos, con tales resultados, se buscaron los casos donde se tuviera un AUC maximo y uno minimo
auc_cases = fn.models_auc(p_models=ml_models, p_global_cases=memory_palace, p_data_folds=t_folds)
# pick case
case = 'max'
# pick model to generate the plot
auc_model = 'ann-mlp'
# get train and test y data
train_y = auc_cases[auc_model]['auc' + '_' + case]['data']['results']['data']['train']
test_y = auc_cases[auc_model]['auc' + '_' + case]['data']['results']['data']['test']
# get data for prices and predictions
ohlc_prices = t_folds[auc_cases[auc_model]['auc' + '_' + case]['period']]
ohlc_class = {'train_y': train_y['y_train'], 'train_y_pred': train_y['y_train_pred'],
'test_y': test_y['y_test'], 'test_y_pred': test_y['y_test_pred']}
# print dataframe
ohlc_prices
# train vline
train_vline = ohlc_prices['timestamp'].iloc[int(round(len(ohlc_prices)*.70, 0))]
# generate title
auc_title = 'max AUC for: ' + auc_model + ' found in period: ' + \
auc_cases[auc_model]['auc_' + case]['period']
# Override plot main title
dt.theme_plot_2['p_labels']['title'] = auc_title
# make plot
plot_en = vs.g_ohlc_class(p_ohlc=ohlc_prices, p_theme=dt.theme_plot_2,
p_data_class=ohlc_class, p_vlines=[train_vline])
# visualize plot
plot_en
En el caso de este ejemplo fue de 1 trimestre de precios diarios, el modelo de la Red neuronal, y el periodo en el que se encontro el "Máximo de máximos" según la cifra de AUC. Podemos observar que las velas rojas fueron los errores del modelo y las azules los aciertos. Del lado izquierdo de la línea vertical se encuentra el periodo de entrenamiento para ese T-Fold y del lado derecho de la línea vertical el periodo de prueba.
# Model accuracy (in sample)
model_acc_train = round(auc_cases[auc_model]['auc' + '_' + case]['data']['metrics']['train']['auc'], 2)
print('The model AUC with train data was: ', model_acc_train)
# Model accuracy (out of sample)
model_acc_test = round(auc_cases[auc_model]['auc' + '_' + case]['data']['metrics']['test']['auc'], 2)
print('\nThe model AUC with test data was: ', model_acc_test)
# Model accuracy (in sample)
model_acc_train = round(auc_cases[auc_model]['auc' + '_' + case]['data']['metrics']['train']['acc']*100, 2)
print('The model accuracy with train data was: ', model_acc_train, '%')
# Model accuracy (out of sample)
model_acc_test = round(auc_cases[auc_model]['auc' + '_' + case]['data']['metrics']['test']['acc']*100, 2)
print('\nThe model accuracy with test data was: ', model_acc_test, '%')
Se presentan las curvas ROC de los "Mejores de los mejores" para cada modelo
# generate title
auc_title = 'ROC de Mejores y Peores Casos (datos de prueba)'
# Override plot main title
dt.theme_plot_4['p_labels']['title'] = auc_title
# generacion de grafica
plot_4_folds = vs.g_roc_auc(p_cases=auc_cases, p_type='test', p_models=ml_models, p_theme=dt.theme_plot_4)
# imprimir grafica
plot_4_folds
minmax_auc_test = {i: {'x_period': [], 'y_mins': [], 'y_maxs': []} for i in ml_models}
# get the cases where auc was min and max in all the periods
for model in ml_models:
minmax_auc_test[model]['x_period'] = list(auc_cases[model]['hof_metrics']['data'].keys())
minmax_auc_test[model]['y_mins'] = [auc_cases[model]['hof_metrics']['data'][periodo]['auc_min']
for periodo in list(auc_cases[model]['hof_metrics']['data'].keys())]
minmax_auc_test[model]['y_maxs'] = [auc_cases[model]['hof_metrics']['data'][periodo]['auc_max']
for periodo in list(auc_cases[model]['hof_metrics']['data'].keys())]
# produce plot
plot_5 = vs.g_timeseries_auc(p_data_auc=minmax_auc_test, p_theme=dt.theme_plot_5)
plot_5
# stable data
data_stables = {model: {'df_auc_max': {period: {} for period in t_folds},
'df_auc_min': {period: {} for period in t_folds}} for model in ml_models}
# periods
period_max_auc = {model: {period: {} for period in t_folds} for model in ml_models}
period_min_auc = {model: {period: {} for period in t_folds} for model in ml_models}
# cycle for getting the parameters of every model
for model in ml_models:
for period in list(t_folds.keys()):
period_max_auc[model][period] = auc_cases[model]['hof_metrics']['data'][period]['auc_max_params']
period_min_auc[model][period] = auc_cases[model]['hof_metrics']['data'][period]['auc_min_params']
# Table 2: Model parameters
table_2 = {'model_1': {'max': pd.DataFrame(period_max_auc['logistic-elasticnet']).T,
'min': pd.DataFrame(period_min_auc['logistic-elasticnet']).T},
'model_2': {'max': pd.DataFrame(period_max_auc['ls-svm']).T,
'min': pd.DataFrame(period_min_auc['ls-svm']).T},
'model_3': {'max': pd.DataFrame(period_max_auc['ann-mlp']).T,
'min': pd.DataFrame(period_min_auc['ann-mlp']).T}}
# evolution of parameters for every model
t_model_1 = table_2['model_1']['max']
t_model_2 = table_2['model_2']['max']
t_model_3 = table_2['model_3']['max']
t_model_3['hidden_layers'] = [str(i) for i in list(t_model_3['hidden_layers'])]
Evolución de parámetros para modelo: Regresión Logística con Regularización Elastic Net (logistic-net)
t_model_1.T
Evolución de parámetros para modelo: Máquina de Soporte Vectorial con Regularización L1
t_model_2.T
Evolución de parámetros para modelo: Red Neuronal Artificial tipo Perceptrón Multicapa
t_model_3.T
Se hizo un analisis de la complejidad espacial de correr el algoritmo para los 3 modelos, encontras los mejores casos, encontrar el mejor de esos mejores casos para cada modelo y encontrar el mejor entre los modelos (todo el proceso anteriormente explicado).
import numpy as np
import matplotlib.pyplot as plt
# object for time keeping
times = {'quarter': {'whole_period': [], 'mean_periods': [], 'periods': []},
'semester': {'whole_period': [], 'mean_periods': [], 'periods': []},
'year': {'whole_period': [], 'mean_periods': [], 'periods': []}}
for size in times.keys():
# in quarters obtain 4 folds for each year
t_folds = fn.t_folds(p_data=general_data.copy(), p_period=size)
if size == 'quarter':
t_folds.pop('q_04_2020', None)
# load data
memory_palace = dt.data_save_load(p_data_objects=None, p_data_action='load',
p_data_file='files/pickle_rick/Genetic_Net_' + size + '.dat')
# list with the names of the models
ml_models = list(dt.models.keys())
period_times = list()
# iterate to have all the times for each period for each model
times_model = {}
for model in ml_models:
times_model[model] = times
for period in list(t_folds.keys()):
period_times.append(memory_palace[model][period]['time'].seconds/60)
times_model[model][size]['whole_period'].append(np.sum(period_times))
times_model[model][size]['periods'].append(period_times)
times_model[model][size]['mean_periods'].append(np.mean(period_times))
ind = [1, 2, 3]
# ajuste polinomial a promedios de valores de count
pl = np.polyfit(x=np.arange(0, len(ind)), y=times['quarter']['whole_period'], deg=1)
pl = np.poly1d(pl)
# construccion de grafica
plt.plot(ind, times['quarter']['whole_period'], 'b*')
plt.plot(ind, pl(np.arange(0, len(ind))), 'r-')
plt.suptitle("Analisis de complejidad $(posteriori)$")
plt.title('logistic-net + l1-svm + ann-mlp', fontsize=10)
plt.legend(["Tiempos", "$O(N)$"])
plt.xlabel("Tamaño DataSet (1=Trim, 2=Sem, 3=Anual)")
plt.ylabel("Minutos en correr 10 años")
# construccion de ecuacion para O(n)
eqn = str(round(list(pl.coefficients)[0], 1)) + 'x' + ' + ' + \
str(round(list(pl.coefficients)[1], 1))
plt.text(1.5, 200, '$y=' + eqn + '$', {'color': 'r', 'fontsize': 8})
# mostrar plot
plt.show()
El resultado que se puede observar es que el tiempo aumentará linealmente conforme se aumente linealmente el tamaño de los data sets, y esto se puede observar con los datos mostrados aún y que los periodos fueron solamente 3 en esta entrega (trimestrales, semestrales, anuales). El tiempo aumenta conforme al tamaño de los data set para hacer todo el proceso diseñado, desde crear los T-Folds, pasando por generación de variables y optimización de hiperparámetros, hasta el cálculo de métricas y visualizaciones.
Es importante notar que el principal resultado de este trabajo es el beneficio de tener un híbrido entre framework y algoritmo. El primero debido a que el marco teórico sobre el uso de variables explicativas lineales y separación de conjuntos de entrenamiento y prueba con T-Folds para series de tiempo es una base conceptual útil para construir las siguientes componentes. Un algoritmo debido a que se considera programación genética, algorítmos genéticos y Machine Learning para la construcción de modelos predictivos, y todo esto, se hace bajo un esquema de ciclos, los cuales, están preponderantemente determinados por el tamaño y cantidad de los conjuntos de entrenamiento, así como de algunas características del proceso de programación genética, como el criterio del fitness, y otros aspectos de los algorítmos genéticos, como la cantidad de generaciones, el Hall of Fame y el criterio de AUC para encontrar el "Mejor entre los mejores".
De acuerdo al presente planteamiento, se puede concluir también que el uso de inteligencia artificial, por si solo, no fue siempre productivo, como se pudo apreciar en los peores casos donde se obtuvo valores de AUC muy bajos. El entender como realizar el proceso de generación de variables fue tan importante como el de optimización de hiperparámetros, y ambos, fueron habilitados gracias al criterio de división de datos.
Como trabajo a futuro se queda el generar mas sub-divisiones de datos para extender el análisis de complejidad.